///////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2007-2009 ILOG, S.A.
//  All Rights Reserved.
//  The following is ILOG Source Code.  No modifications may be made to the  
//  ILOG Source Code.  Any usage of the ILOG Source Code is subject to 
//  the terms and conditions of the ILOG End User License Agreement   
//  applicable to this ILOG software product.
//
///////////////////////////////////////////////////////////////////////////////

package ilog.gauges.gaugesClasses.circular
{
  import flash.events.Event;
  
  import ilog.gauges.circular.CircularGauge;
  import ilog.gauges.circular.renderers.CircularLabelRenderer;
  import ilog.gauges.circular.renderers.CircularScaleRenderer;
  import ilog.gauges.circular.renderers.CircularTrackRenderer;
  import ilog.utils.CSSUtil;
  
  import mx.graphics.GradientEntry;
  import mx.styles.CSSStyleDeclaration;
  
  [Exclude(name="elements", kind="property")]
  [Exclude(name="scales", kind="property")]     

  /**
   * Dispatched when the gauge value changes.
   *
   * @eventType flash.events.Event.CHANGE
   * 
   */
  [Event(name="change", type="flash.events.Event")]

  /**
   * Controls the visibility of the value indicator label.
   *  
   * @default true
   */
  [Style(name="showValueIndicator", type="Boolean", inherit="no")]
  
  /**
   * A style name to be used for the value indicator label. If <code>null</code>
   * the indicator label will use the style of the gauge itself.
   * 
   * @default null
   * @since ILOG Elixir 2.0
   * @ilog.reviewed SH April 24 08
   */
  [Style(name="valueIndicatorStyleName", type="String", inherit="no")]

  /**
   * Controls the visibility of the track.
   *  
   * @default true
   */
  [Style(name="showTrack", type="Boolean", inherit="no")]

  /**
   * The length, in milliseconds, 
   * of a transition when changing the <code>value</code>. 
   * 
   * @default 500
   */
  [Style(name="animationDuration", type="Number", format="Time", inherit="no")]  

  /**
   * This is a base class for circular gauges that renders a single
   * value on a single scale.
   * This class defines a circular gauge with the following elements:
   * <ul>
   *   <li>a value renderer (typically a needle)</li> 
   *   <li>a label, which shows the current value of the gauge</li> 
   *   <li>a default background, with customizable colors</li>
   * </ul>
   * <p>
   * You do not use the <code>SingleCircularGauge</code> class 
   * directly, even if it is possible.
   * </p> 
   * @mxml
   *  
   * <p>The <code>&lt;SingleCircularGauge&gt;</code> tag inherits all the properties
   * of its parent classes and adds the following properties:</p>
   *  
   * <pre>
   * &lt;SingleCircularGauge
   *   <strong>Properties</strong>
   *   indicatorLabelFunction="<i>Function</i>"
   *   backgroundElements="<i>Array, No default</i>"
   *   foregroundElements="<i>Array, No default</i>"
   *   valueRenderer="<i>CircularValueRenderer, No default</i>"
   *   scaleRenderer="<i>A default scale renderer</i>"
   *   track="<i>A default circular track</i>"
   *   editable="false|true"
   *   editMode="discrete|continuous"
   *   mouseMode="area|shape"
   * 
   *   <strong>Styles</strong>
   *   showValueIndicator="true|false" 
   *   valueIndicatorStyleName="null"
   *   showTrack="true|false"
   * /&gt;
   * </pre>
   */  
  public class SingleCircularGauge extends CircularGauge
  {
    /**
     * @private 
     */
    private var _valueRendererParam:Object = {
      editable: false,
      editMode: "discrete",
      mouseMode: "area",
      dirty: false
    };

    /**
     * @private 
     */     
    private static var stylesInited:Boolean = initStyles(); 

    /**
     * @private 
     */ 
    private static function initStyles():Boolean
    {
      var style:CSSStyleDeclaration =
        CSSUtil.createSelector("SingleCircularGauge");   
            
      style.defaultFactory = function():void {
        this.showTrack = true;
        this.showValueIndicator = true;
        this.animationDuration = 500;
        this.valueIndicatorStyleName = null;
      }
      return true;
    }    

    /**
     * @private 
     */ 
    private var _elementsDirty:Boolean = false;

    /**
     * @private 
     */ 
    private var _valueLabel:CircularLabelRenderer;    
    
    /**
     * Constructor 
     */ 
    public function SingleCircularGauge()
    {
      super();
      _valueLabel = new CircularLabelRenderer();
    }    

    [Inspectable(category="Gauge")]
    
    /**
     * @copy ilog.gauges.gaugesClasses.circular.CircularValueRenderer#editable
     * @default false
     */       
    public function get editable():Boolean
    {
      return _valueRendererParam.editable;
    }

    /**
     * @private 
     */
    public function set editable(value:Boolean):void
    {
      _valueRendererParam.editable = value;
      _valueRendererParam.dirty = true;
      invalidateProperties();
    }

    [Inspectable(category="Gauge", enumeration="continuous,discrete")]
    
    /**
     *  @copy ilog.gauges.gaugesClasses.circular.CircularValueRenderer#editMode
     *  @default "discrete"
     */
    public function get editMode():String
    {
      return _valueRendererParam.editMode;
    }

    /**
     * @private 
     */
    public function set editMode(value:String):void
    {
      _valueRendererParam.editMode = value;
      _valueRendererParam.dirty = true;
      invalidateProperties();
    }

    [Inspectable(category="Gauge", enumeration="shape,area")]
    
    /**
     *  @copy ilog.gauges.gaugesClasses.circular.CircularValueRenderer#mouseMode
     *  @default "area"
     */
    public function get mouseMode():String
    {
      return _valueRendererParam.mouseMode;
    }

    /**
     * @private 
     */
    public function set mouseMode(value:String):void
    {
      _valueRendererParam.mouseMode = value;
      _valueRendererParam.dirty = true;
      invalidateProperties();
    }
    
    /**
     * @private 
     */ 
    private var _track:CircularTrackRenderer;

    [Bindable]
    [Inspectable(category="Gauge")]
    
    /**
     * A track. 
     * 
     * @default ilog.gauges.circular.renderers.CircularTrackRenderer
     */     
    public function get track():CircularTrackRenderer
    {
      return _track;
    }

    /**
     * @private 
     */     
    public function set track(value:CircularTrackRenderer):void 
    {
      _track = value;
      invalidateElements();
    }

    /**
     * @private 
     */ 
    private var _scaleRenderer:CircularScaleRenderer;

    [Inspectable(category="Gauge")]    
  
    /**
     * The scale renderer of the gauge. 
     * 
     * @default ilog.gauges.circular.renderers.CircularScaleRenderer
     */ 
    public function get scaleRenderer():CircularScaleRenderer
    {
      return _scaleRenderer;
    }

    /**
     * @private 
     */     
    public function set scaleRenderer(renderer:CircularScaleRenderer):void
    {
      _scaleRenderer = renderer;
      _scaleRenderer.styleName = this;
      invalidateElements();
    }

    /**
     * @private 
     */ 
    private var _valueRenderer:CircularValueRenderer;

    [Bindable]
    [Inspectable(category="Gauge")]    
    
    /**
     * The value renderer of the gauge. Typically, a <code>NeedleRenderer</code>
     * or a <code>CircularBarRenderer</code>.
     * 
     * @default null 
     */     
    public function get valueRenderer():CircularValueRenderer
    {
      return _valueRenderer;
    }
    
    /**
     * @private 
     */     
    public function set valueRenderer(renderer:CircularValueRenderer):void
    {
      if (_valueRenderer != null) {
        _valueRenderer.removeEventListener("change", changeListener);
        _valueRenderer.removeEventListener("valueCommit", changeListener);
      }
      _valueRenderer = renderer;
      invalidateElements();
      if (_valueRenderer != null) {
        _valueRenderer.addEventListener("change", changeListener);
        _valueRenderer.addEventListener("valueCommit", changeListener);
      }
      _valueRendererParam.dirty = true;
      invalidateProperties();      
    }

    /**
     * @private 
     */     
    private function changeListener(e:Event):void
    {
      if (indicatorLabelFunction != null)    
        _valueLabel.text = indicatorLabelFunction(valueRenderer.value);
      else
        _valueLabel.text = isNaN(Number(valueRenderer.value)) ? "" : String(valueRenderer.value);
      dispatchEvent(e);
    }

    /**
     * @private 
     */     
    private var _indicatorLabelFunction:Function;

    [Inspectable(category="Gauge")]    

    /**
     * The function called to format the text of the value indicator label.
     * An <code>indicatorLabelFunction</code> must have the following signature:
     * <pre>
     * function indicatorLabelFunction(value:Object):String { ... }
     * </pre>
     * If <code>indicatorLabelFunction</code> is <code>null</code>, the value is converted into a 
     * <code>String</code> object to be displayed.
     * @default null
     * @ilog.reviewed SH 24 Oct 08
     */
    public function get indicatorLabelFunction():Function
    {
      return _indicatorLabelFunction;
    }

    /**
     * @private 
     */     
    public function set indicatorLabelFunction(value:Function):void
    {
      _indicatorLabelFunction = value;
    }

    /**
     * @private 
     */     
    private var _backgroundElements:Array = [];
    
    [Inspectable(category="Gauge")]
        
    /**
     * An array of background <code>CircularGaugeElement</code> objects that 
     * appear behind the scale, value renderer, track, and labels.
     * @default []
     */
    public function get backgroundElements():Array
    {
      return _backgroundElements;        
    }

    /**
     * @private 
     */     
    public function set backgroundElements(value:Array):void
    {
      _backgroundElements = value; 
      invalidateElements();
    }

    /**
     * @private 
     */   
    private var _foregroundElements:Array = [];

    [Inspectable(category="Gauge")]

    /**
     * An array of foreground <code>CircularGaugeElement</code> objects that 
     * appear in front of the scale, value renderer, track, and labels.
     * @default []
     */
    public function get foregroundElements():Array
    {
      return _foregroundElements;
    }
    
    /**
     * @private 
     */    
    public function set foregroundElements(value:Array):void
    {
      _foregroundElements = value;
      invalidateElements();
    }         
    
    /**
     * The renderer of the value label. 
     */     
    protected function get valueLabel():CircularLabelRenderer
    {
      return _valueLabel;
    }

    /**
     * This method is called to specify the layout of the predefined elements of 
     * the gauge. You can redefine the layout to position the elements differently.
     */
    protected function layoutElements():void
    {
      _valueLabel.percentOriginY = 75;       
    }
    

    /**
     * Concatenates the various elements of the gauge and sets 
     * the <code>elements</code> property to the result. You can redefine this method
     * to customize the concatenation.
     * 
     * @see ilog.gauges.gaugesClasses.GaugeBase#elements
     */    
    protected function validateElements():void
    {
      var showTrackStyle:Object = getStyle("showTrack");
      var showTrack:Boolean = showTrackStyle != false &&
        showTrackStyle != "false";
                                                                 
      var showValueIndicatorStyle:Object = getStyle("showValueIndicator");
      var showValueIndicator:Boolean = showValueIndicatorStyle != false &&
        showValueIndicatorStyle != "false";
 
      var elts:Array = [];
      
      if (_backgroundElements && backgroundElements.length > 0)
        elts = elts.concat(_backgroundElements);

      if (showTrack == true && _track)
        elts = elts.concat(_track);

      if (_scaleRenderer)
        elts = elts.concat(_scaleRenderer);
      
      if (_valueRenderer)
        elts = elts.concat(_valueRenderer);
      
      if (_foregroundElements && foregroundElements.length > 0)
        elts = elts.concat(_foregroundElements);

      if (showValueIndicator == true && _valueLabel)
        elts = elts.concat(_valueLabel);
      
      elements = elts;
      layoutElements();
    }

    /**
     * Invalidates the <code>elements</code> array so that <code>validateElements</code>
     * is called.
     * 
     * @see ilog.gauges.gaugesClasses.GaugeBase#elements
     * @see #validateElements()
     */        
    protected function invalidateElements():void
    {
      _elementsDirty = true;
      invalidateProperties();
    }
    
    /**
     * @private 
     */ 
    override public function styleChanged(styleProp:String):void
    {
      super.styleChanged(styleProp);
      // if the style props in one of the showXXX properties
      // let's rebuild the elements array accordingly
      if (styleProp != null && styleProp.indexOf("show") != -1) {
        invalidateElements();
        invalidateDisplayList();
      }
      
      if (styleProp == null || styleProp == "valueIndicatorStyleName") {
        _valueLabel.styleName = getStyle("valueIndicatorStyleName");
      }
      
      if (styleProp == "animationDuration") {
        _valueRendererParam.dirty = true;
        invalidateProperties();
      }  
    }
    
    /**
     * @private
     */
    protected override function commitProperties():void
    {
      if (_elementsDirty) {
        validateElements();
        _elementsDirty = false;
      }
      
      if (valueRenderer && _valueRendererParam.dirty) {
        valueRenderer.editable = _valueRendererParam.editable;
        valueRenderer.editMode = _valueRendererParam.editMode;
        valueRenderer.mouseMode = _valueRendererParam.mouseMode;
        valueRenderer.setStyle("animationDuration", getStyle("animationDuration"));
        _valueRendererParam.dirty = false;
      }
      super.commitProperties();
    }
    
    /**
     * @private
     */
    protected override function createChildren():void
    {
      super.createChildren();
      if (track == null) {  
        track = new CircularTrackRenderer();
        track.minimum = 50;
        track.maximum = 100;
        track.percentRadius = 25;
        
        var ge:Array = [];
        ge.push(new GradientEntry(0x00DD00, 0));
        ge.push(new GradientEntry(0xDDDD00, 0.5));
        ge.push(new GradientEntry(0xDD0000, 1.0));
        track.gradientEntries = ge;  
      }
      if (scaleRenderer == null) {
        scaleRenderer = new CircularScaleRenderer(); 
        scaleRenderer.percentRadius = 45;
        scaleRenderer.percentLabelRadius = 88;
      }
    }
  }
}
